/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.web.util;

import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * @author Dillon
 * @date 2024/6/30
 * @slogan 致敬大师 致敬未来的你
 * @desc springSecurity 过滤器链异常分析器
 * 主要处理抛出的异常类型，可以针对于异常类型做特殊处理
 * {@link org.springframework.security.web.access.ExceptionTranslationFilter} 该过滤器就是基于不同的异常来进行不同的逻辑处理
 */
public class ThrowableAnalyzer {

	/**
	 * 默认 Throwable 提取异常
	 */
	public static final ThrowableCauseExtractor DEFAULT_EXTRACTOR = Throwable::getCause;

	/**
	 * 默认的 InvocationTargetException 提取异常
	 */
	public static final ThrowableCauseExtractor INVOCATIONTARGET_EXTRACTOR = (throwable) -> {
		verifyThrowableHierarchy(throwable, InvocationTargetException.class);
		return ((InvocationTargetException) throwable).getTargetException();
	};

	/**
	 * 根据类层次排序，
	 * 如果有父子关系 则认为父类排前面
	 * 如果么没有，按照类限定名排序
	 */
	private static final Comparator<Class<? extends Throwable>> CLASS_HIERARCHY_COMPARATOR = (class1, class2) -> {
		if (class1.isAssignableFrom(class2)) {
			return 1;
		}
		if (class2.isAssignableFrom(class1)) {
			return -1;
		}
		return class1.getName().compareTo(class2.getName());
	};

	/**
	 * 待提取异常类列表
	 */
	private final Map<Class<? extends Throwable>, ThrowableCauseExtractor> extractorMap;

	/**
	 * 构造函数
	 */
	public ThrowableAnalyzer() {
		// 初始化 异常类列表Map 按照一定方式排序
		this.extractorMap = new TreeMap<>(CLASS_HIERARCHY_COMPARATOR);
		initExtractorMap();
	}

	/**
	 * 注册异常提取类到Map中
	 *
	 * @param throwableType 异常class类
	 * @param extractor     异常提取方式执行器
	 */
	protected final void registerExtractor(Class<? extends Throwable> throwableType, ThrowableCauseExtractor extractor) {
		Assert.notNull(extractor, "异常不能为空");
		this.extractorMap.put(throwableType, extractor);
	}

	/**
	 * 注册 异常类提取Map
	 * 默认注册 InvocationTargetException Throwable 两个提取异常
	 */
	protected void initExtractorMap() {
		registerExtractor(InvocationTargetException.class, INVOCATIONTARGET_EXTRACTOR);
		registerExtractor(Throwable.class, DEFAULT_EXTRACTOR);
	}

	/**
	 * 获取已注册代提取异常class列表
	 *
	 * @return 待提取异常class数组
	 */
	@SuppressWarnings("unchecked")
	final Class<? extends Throwable>[] getRegisteredTypes() {
		Set<Class<? extends Throwable>> typeList = this.extractorMap.keySet();
		return typeList.toArray(new Class[0]);
	}

	/**
	 * 拆解异常信息 生成异常列表
	 *
	 * @param throwable 异常对象
	 * @return 待解析异常对象数组
	 */
	public final Throwable[] determineCauseChain(Throwable throwable) {
		Assert.notNull(throwable, "异常对象不能为空");
		List<Throwable> chain = new ArrayList<>();
		Throwable currentThrowable = throwable;
		while (currentThrowable != null) {
			chain.add(currentThrowable);
			currentThrowable = extractCause(currentThrowable);
		}
		return chain.toArray(new Throwable[0]);
	}

	/**
	 * 尝试处理 其他待提取异常
	 * 如果自定义异常提取，那么这里会执行额外异常提取
	 *
	 * @param throwable 抛出的异常对象
	 * @return 匹配到的异常class
	 */
	private Throwable extractCause(Throwable throwable) {
		for (Map.Entry<Class<? extends Throwable>, ThrowableCauseExtractor> entry : this.extractorMap.entrySet()) {
			Class<? extends Throwable> throwableType = entry.getKey();
			if (throwableType.isInstance(throwable)) {
				ThrowableCauseExtractor extractor = entry.getValue();
				return extractor.extractCause(throwable);
			}
		}
		return null;
	}

	/**
	 * @param throwableType 目标异常
	 * @param chain         解析出的异常数组
	 * @return 目标异常 匹配不到 返回null
	 */
	public final Throwable getFirstThrowableOfType(Class<? extends Throwable> throwableType, Throwable[] chain) {
		if (chain != null) {
			for (Throwable t : chain) {
				if ((t != null) && throwableType.isInstance(t)) {
					return t;
				}
			}
		}
		return null;
	}

	/**
	 * 校验异常之间是否存在父子关系
	 *
	 * @param throwable        子异常
	 * @param expectedBaseType 父异常
	 */
	public static void verifyThrowableHierarchy(Throwable throwable, Class<? extends Throwable> expectedBaseType) {
		if (expectedBaseType == null) {
			return;
		}
		Assert.notNull(throwable, "Invalid throwable: null");
		Class<? extends Throwable> throwableType = throwable.getClass();
		Assert.isTrue(expectedBaseType.isAssignableFrom(throwableType), () -> "Invalid type: '"
				+ throwableType.getName() + "'. Has to be a subclass of '" + expectedBaseType.getName() + "'");
	}

}
